package de.codi.similarity.filler;

import java.util.HashSet;
import java.util.Set;

import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLDataRange;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.reasoner.NodeSet;
import org.semanticweb.owlapi.reasoner.OWLReasoner;

import org.semanticweb.owlapi.reasoner.Node;

import de.codi.read.Storage;


public class StructuralFiller
{
    public static double fill(OWLEntity e1, OWLEntity e2)
    {
        double conf = 0;

        OWLReasoner r1 = Storage.source.reasoner;
        OWLReasoner r2 = Storage.target.reasoner;

        OWLOntology o1 = Storage.source.ont;
        OWLOntology o2 = Storage.target.ont;

        // double o1NumOfClasses = o1.getClassesInSignature().size() + 1;
        // double o2NumOfClasses = o2.getClassesInSignature().size() + 1;

        double o1NumOfOProperties = o1.getObjectPropertiesInSignature().size() + 1;
        double o2NumOfOProperties = o2.getObjectPropertiesInSignature().size() + 1;

        double o1NumOfDProperties = o1.getDataPropertiesInSignature().size() + 1;
        double o2NumOfDProperties = o2.getDataPropertiesInSignature().size() + 1;

        double o1NumOfProperties = o1NumOfOProperties + o1NumOfDProperties;
        double o2NumOfProperties = o2NumOfOProperties + o2NumOfDProperties;

        // OLD:
        // int ref1 = e1.getReferencingAxioms(o1).size();
        // int ref2 = e2.getReferencingAxioms(o2).size();

        // concepts
        if(e1 instanceof OWLClass && e2 instanceof OWLClass) {
            int super1 = r1.getSuperClasses((OWLClass) e1, false).getNodes().size();
            int super2 = r2.getSuperClasses((OWLClass) e2, false).getNodes().size();

            int sub1 = r1.getSubClasses((OWLClass) e1, false).getNodes().size();
            int sub2 = r2.getSubClasses((OWLClass) e2, false).getNodes().size();

            int domain1 = 0;
            int range1 = 0;
            int domain2 = 0;
            int range2 = 0;

            for(OWLObjectProperty oprop : Storage.source.ont.getObjectPropertiesInSignature()) {
                NodeSet<OWLClass> rangeSet1 = r1.getObjectPropertyRanges(oprop, false);
                NodeSet<OWLClass> domainSet1 = r1.getObjectPropertyDomains(oprop, false);
                if(rangeSet1.containsEntity((OWLClass) e1))
                    range1++;
                if(domainSet1.containsEntity((OWLClass) e1))
                    domain1++;
            }

            for(OWLObjectProperty oprop : Storage.target.ont.getObjectPropertiesInSignature()) {
                NodeSet<OWLClass> rangeSet2 = r2.getObjectPropertyRanges(oprop, false);
                NodeSet<OWLClass> domainSet2 = r2.getObjectPropertyDomains(oprop, false);
                if(rangeSet2.containsEntity((OWLClass) e2))
                    range2++;
                if(domainSet2.containsEntity((OWLClass) e2))
                    domain2++;
            }

            for(OWLDataProperty dprop : Storage.source.ont.getDataPropertiesInSignature()) {
                NodeSet<OWLClass> domainSet1 = r1.getDataPropertyDomains(dprop, false);
                if(domainSet1.containsEntity((OWLClass) e1))
                    domain1++;
            }

            for(OWLDataProperty dprop : Storage.target.ont.getDataPropertiesInSignature()) {
                NodeSet<OWLClass> domainSet2 = r2.getDataPropertyDomains(dprop, false);
                if(domainSet2.containsEntity((OWLClass) e2))
                    domain2++;
            }
            // old formula
            // conf = ((double) 1) / (100 * (1 + Math.pow(ref1 - ref2, 2) + Math.pow(domain1 - domain2, 2) + Math.pow(range1 - range2, 2) + Math.pow(super1 - super2, 2) + Math.pow(sub1 - sub2, 2)));
            // new formula

            if(o1NumOfProperties == 0 || o2NumOfProperties == 0) {
                conf = divMinMax(sub1, sub2) + divMinMax(super1, super2);
                conf = conf / 2;
            } else {
                conf = divMinMax(sub1, sub2) + divMinMax(super1, super2) + divMinMax(domain1, domain2) + divMinMax(range1, range2);
                conf = conf / 4;
            }

        }
        // object properties
        if(e1 instanceof OWLObjectProperty && e2 instanceof OWLObjectProperty) {
            // System.out.println("Properties: " + e1 + " = " + e2);
            int super1 = r1.getSuperObjectProperties((OWLObjectProperty) e1, false).getNodes().size();
            int super2 = r2.getSuperObjectProperties((OWLObjectProperty) e2, false).getNodes().size();
            int sub1 = r1.getSubObjectProperties((OWLObjectProperty) e1, false).getNodes().size();
            int sub2 = r2.getSubObjectProperties((OWLObjectProperty) e2, false).getNodes().size();

            int subDomain1 = getSubOfNodes(r1, r1.getObjectPropertyDomains((OWLObjectProperty) e1, false).getNodes());
            int subDomain2 = getSubOfNodes(r2, r2.getObjectPropertyDomains((OWLObjectProperty) e2, false).getNodes());

            int superDomain1 = getSuperOfNodes(r1, r1.getObjectPropertyDomains((OWLObjectProperty) e1, false).getNodes());
            int superDomain2 = getSuperOfNodes(r2, r2.getObjectPropertyDomains((OWLObjectProperty) e2, false).getNodes());

            int subRange1 = getSubOfNodes(r1, r1.getObjectPropertyRanges((OWLObjectProperty) e1, false).getNodes());
            int subRange2 = getSubOfNodes(r2, r2.getObjectPropertyRanges((OWLObjectProperty) e2, false).getNodes());

            int superRange1 = getSuperOfNodes(r1, r1.getObjectPropertyRanges((OWLObjectProperty) e1, false).getNodes());
            int superRange2 = getSuperOfNodes(r2, r2.getObjectPropertyRanges((OWLObjectProperty) e2, false).getNodes());

            // old formula
            // conf = ((double) 1) / (100 * (1 + Math.pow(ref1 - ref2, 2) + Math.pow(domain1 - domain2, 2) + Math.pow(range1 - range2, 2) + Math.pow(super1 - super2, 2) + Math.pow(sub1 - sub2, 2)));
            // total = domain1 + domain2 + range1 + range2 + super1 + super2 + sub1 + sub2;
            // new formula
            conf = divMinMax(sub1, sub2) + divMinMax(super1, super2) + divMinMax(subDomain1, subDomain2) + divMinMax(subRange1, subRange2) + divMinMax(superDomain1, superDomain2) + divMinMax(superRange1, superRange2);
            conf = conf / 6;

            // System.out.println("ObjectProperty: " + e1 + "|" + e2 + ": " + conf);

        }

        // data properties
        if(e1 instanceof OWLDataProperty && e2 instanceof OWLDataProperty) {
            int super1 = r1.getSuperDataProperties((OWLDataProperty) e1, false).getNodes().size();
            int super2 = r2.getSuperDataProperties((OWLDataProperty) e2, false).getNodes().size();
            int sub1 = r1.getSubDataProperties((OWLDataProperty) e1, false).getNodes().size();
            int sub2 = r2.getSubDataProperties((OWLDataProperty) e2, false).getNodes().size();

            int subDomain1 = getSubOfNodes(r1, r1.getDataPropertyDomains((OWLDataProperty) e1, false).getNodes());
            int subDomain2 = getSubOfNodes(r2, r2.getDataPropertyDomains((OWLDataProperty) e2, false).getNodes());

            int superDomain1 = getSuperOfNodes(r1, r1.getDataPropertyDomains((OWLDataProperty) e1, false).getNodes());
            int superDomain2 = getSuperOfNodes(r2, r2.getDataPropertyDomains((OWLDataProperty) e2, false).getNodes());

            // not clear if this really works, has to be tested
            Set<OWLDataRange> ranges1 = ((OWLDataProperty) e1).getRanges(o1);
            Set<OWLDataRange> ranges2 = ((OWLDataProperty) e2).getRanges(o2);

            boolean sameDRT = false;
            for(OWLDataRange range1 : ranges1) {
                for(OWLDataRange range2 : ranges2) {
                    if(range1.equals(range2)) {
                        sameDRT = true;
                    }
                }
            }

            // old formula
            // conf = ((double) 1) / (100 * (1 + Math.pow(ref1 - ref2, 2) + Math.pow(domain1 - domain2, 2) + Math.pow((sameDRT ? 0 : 2), 2) + Math.pow(super1 - super2, 2) + Math.pow(sub1 - sub2, 2)));
            // total = domain1 + domain2 + super1 + super2 + sub1 + sub2;
            // new formula
            conf = divMinMax(sub1, sub2) + divMinMax(super1, super2) + divMinMax(subDomain1, subDomain2) + divMinMax(superDomain1, superDomain2) + divMinMax(sameDRT ? 1 : 0, 1);
            conf = conf / 5;
        }
        // if (conf < 0.002) { return 0; }

        //if(conf > 0.8) {
            //System.out.println("DataProperty: " + e1 + "|" + e2 + ": " + conf);
        //}
        return conf;
    }


    public static int getSubOfNodes(OWLReasoner r, Set<Node<OWLClass>> nodes)
    {
        HashSet<OWLClass> subClasses = new HashSet<OWLClass>();
        for(Node<OWLClass> node : nodes) {
            for(OWLClass c : node.getEntities()) {
                for(Node<OWLClass> subnode : r.getSubClasses(c, false).getNodes()) {
                    subClasses.addAll(subnode.getEntities());
                }
            }
        }
        return subClasses.size();
    }


    public static int getSuperOfNodes(OWLReasoner r, Set<Node<OWLClass>> nodes)
    {
        HashSet<OWLClass> superClasses = new HashSet<OWLClass>();
        for(Node<OWLClass> node : nodes) {
            for(OWLClass c : node.getEntities()) {
                for(Node<OWLClass> supernode : r.getSuperClasses(c, false).getNodes()) {
                    superClasses.addAll(supernode.getEntities());
                }
            }
        }
        return superClasses.size();
    }


    public static double divMinMax(double a, double b)
    {
        double min = (double) Math.min(a, b);
        double max = (double) Math.max(a, b);
        return ((1.0 + min) / (1.0 + max)) * 0.01;
    }

}